#include <bits/stdc++.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <pthread.h>
#include "cmdline.h"
using namespace std;
typedef pair<string, struct stat> P;
typedef pair<string, string> PS;
vector<dirent> dirs, files;
vector<P> dirpwds, filepwds;
vector<PS> lnkpwds;
pthread_mutex_t m_mutex;
int fdx = 0;
string src, dist;
bool enterdirs(string path, int ts) {
  DIR* dirptr = opendir(path.c_str());
  dirent* entry = 0;
  struct stat buf;
  if (dirptr == NULL) {
    return false;
  } else {
    while (entry = readdir(dirptr)) {
      if (string(entry->d_name) == "." || string(entry->d_name) == "..") {
        continue;
      } else if ((entry->d_type & DT_LNK) == DT_LNK) {
        string pwd = path + string(entry->d_name);
        lnkpwds.push_back(PS(pwd, path));
      } else if (entry->d_type & DT_DIR) {
        // 文件夹路径
        dirs.push_back(*entry);
        string pwd = path + string(entry->d_name) + "/";
        lstat(pwd.c_str(), &buf);
        dirpwds.push_back(P(pwd, buf));
        enterdirs(path + string(entry->d_name) + "/", ts + 1);
      } else {
        // 文件路径
        files.push_back(*entry);
        string pwd = path + string(entry->d_name);
        lstat(pwd.c_str(), &buf);
        filepwds.push_back(P(pwd, buf));
      }
    }
  }
  closedir(dirptr);
  return true;
}

void mkdirp(string path, mode_t mode) {
  string pwd = "";
  for (auto i : path) {
    pwd += i;
    if (i == '/') {
      if (access(pwd.c_str(), F_OK) != 0) {
        mkdir(pwd.c_str(), mode);
      }
    }
  }
}

void mkdirs() {
  for (int i = 0; i < dirs.size(); i++) {
    string pwd = dirpwds[i].first;
    pwd = pwd.substr(src.size(), pwd.size() - src.size());
    pwd = dist + pwd;
    cout << "create " << pwd << endl;
    mkdir(pwd.c_str(), dirpwds[i].second.st_mode);
  }
}

void mklnks() {
  for (int i = 0; i < lnkpwds.size(); i++) {
    string pathname = lnkpwds[i].first;
    char buf[1024];
    int sz = readlink(pathname.c_str(), buf, sizeof(buf));
    buf[sz] = 0;
    string actualpath = lnkpwds[i].second + string(buf);
    actualpath = actualpath.substr(src.size(), actualpath.size() - src.size());
    actualpath = dist + actualpath;
    string sympath = lnkpwds[i].first;
    sympath = sympath.substr(src.size(), sympath.size() - src.size());
    sympath = dist + sympath;
    cout << "symlink: " << sympath << "->" << actualpath << endl;
    symlink(actualpath.c_str(), sympath.c_str());
  }
}

void* copy_thread(void* p_th) {
  while(true) {
    pthread_mutex_lock(&m_mutex);
    if (fdx >= filepwds.size()) {
      pthread_mutex_unlock(&m_mutex);
      return 0;
    }
    P p = filepwds[fdx];
    fdx++;
    pthread_mutex_unlock(&m_mutex);
    string psrc = p.first;
    string pdist = psrc;
    pdist = pdist.substr(src.size(), pdist.size() - src.size());
    pdist = dist + pdist;
    cout << "copy " << psrc << " to " << pdist << endl;
    ifstream in(psrc, ios::binary);
    ofstream out(pdist, ios::binary);
    out << in.rdbuf();
    in.close();
    out.close();
  }
  return 0;
}

int main(int argc, char** argv) {
  cmdline::parser arg;
  arg.add<string>("src", 's', "source dir", true, "");
  arg.add<string>("dist", 'd', "distinction dir", true, "");
  arg.add<int>("processes", 'p', "number of processes", true, 4, cmdline::range(1, 64));
  arg.parse_check(argc, argv);
  src = arg.get<string>("src");
  dist = arg.get<string>("dist");
  int processes = arg.get<int>("processes");

  if (src.size() == 0 || dist.size() == 0) {
    exit(1);
  }

  if (src[src.size() - 1] != '/') {
    src = src + "/";
  }

  if (dist[dist.size() - 1] != '/') {
    dist = dist + "/";
  }

  if (src == dist) {
    return 0;
  }

  // 首先按照源目录属性创建深层目标文件夹
  struct stat buf;
  lstat(src.c_str(), &buf);
  mkdirp(dist.c_str(), buf.st_mode);

  enterdirs(src, 0);

  cout << "copy dirs: " << dirs.size() << endl;
  cout << "copy files: " << files.size() << endl;
  cout << "copy links:" << lnkpwds.size() << endl;

  /* 单线程按照遍历顺序创建目录，避免出现目录依赖问题 */
  mkdirs();

  // 多线程复制文件
  pthread_mutex_init(&m_mutex, NULL);
  vector<pthread_t> pool;
  vector<int> assign;
  for (int i = 0; i < processes; i++) {
    assign.push_back(i);
  }
  for (int i = 0; i < processes; i++) {
    pthread_t pid;
    pthread_create(&pid, NULL, copy_thread, &assign[i]);
    pool.push_back(pid);
  }
  for (auto i : pool) {
    pthread_join(i, NULL);
  }
  pthread_mutex_destroy(&m_mutex);

  // 单线程创建符号链接
  mklnks();

  return 0;
}